/*
 *  Copyright (c) 2001 Sun Microsystems, Inc.  All rights
 *  reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions
 *  are met:
 *
 *  1. Redistributions of source code must retain the above copyright
 *  notice, this list of conditions and the following disclaimer.
 *
 *  2. Redistributions in binary form must reproduce the above copyright
 *  notice, this list of conditions and the following disclaimer in
 *  the documentation and/or other materials provided with the
 *  distribution.
 *
 *  3. The end-user documentation included with the redistribution,
 *  if any, must include the following acknowledgment:
 *  "This product includes software developed by the
 *  Sun Microsystems, Inc. for Project JXTA."
 *  Alternately, this acknowledgment may appear in the software itself,
 *  if and wherever such third-party acknowledgments normally appear.
 *
 *  4. The names "Sun", "Sun Microsystems, Inc.", "JXTA" and "Project JXTA"
 *  must not be used to endorse or promote products derived from this
 *  software without prior written permission. For written
 *  permission, please contact Project JXTA at http://www.jxta.org.
 *
 *  5. Products derived from this software may not be called "JXTA",
 *  nor may "JXTA" appear in their name, without prior written
 *  permission of Sun.
 *
 *  THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 *  WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 *  OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 *  DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
 *  ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 *  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 *  LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 *  USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 *  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 *  OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 *  OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 *  SUCH DAMAGE.
 *  ====================================================================
 *
 *  This software consists of voluntary contributions made by many
 *  individuals on behalf of Project JXTA.  For more
 *  information on Project JXTA, please see
 *  <http://www.jxta.org/>.
 *
 *  This license is based on the BSD license adopted by the Apache Foundation.
 *
 *  $Id: Graph.java,v 1.8 2006/10/09 22:17:52 nano Exp $
 */

package net.jxta.myjxta.ui;

import edu.berkeley.guir.prefuse.*;
import edu.berkeley.guir.prefuse.action.RepaintAction;
import edu.berkeley.guir.prefuse.action.animate.ColorAnimator;
import edu.berkeley.guir.prefuse.action.animate.PolarLocationAnimator;
import edu.berkeley.guir.prefuse.action.assignment.ColorFunction;
import edu.berkeley.guir.prefuse.action.filter.FisheyeTreeFilter;
import edu.berkeley.guir.prefuse.action.filter.TreeEdgeFilter;
import edu.berkeley.guir.prefuse.action.filter.TreeFilter;
import edu.berkeley.guir.prefuse.activity.ActionList;
import edu.berkeley.guir.prefuse.activity.SlowInSlowOutPacer;
import edu.berkeley.guir.prefuse.event.ControlAdapter;
import edu.berkeley.guir.prefuse.event.FocusEvent;
import edu.berkeley.guir.prefuse.event.FocusListener;
import edu.berkeley.guir.prefuse.graph.*;
import edu.berkeley.guir.prefuse.graph.event.GraphLoaderListener;
import edu.berkeley.guir.prefuse.graph.external.GraphLoader;
import edu.berkeley.guir.prefuse.render.DefaultEdgeRenderer;
import edu.berkeley.guir.prefuse.render.DefaultRendererFactory;
import edu.berkeley.guir.prefuse.render.ShapeRenderer;
import edu.berkeley.guir.prefuse.render.TextImageItemRenderer;
import edu.berkeley.guir.prefuse.util.ColorMap;
import edu.berkeley.guir.prefusex.controls.*;
import edu.berkeley.guir.prefusex.force.DragForce;
import edu.berkeley.guir.prefusex.force.ForceSimulator;
import edu.berkeley.guir.prefusex.force.NBodyForce;
import edu.berkeley.guir.prefusex.layout.ForceDirectedLayout;
import edu.berkeley.guir.prefusex.layout.RadialTreeLayout;
import net.jxta.myjxta.MyJXTA;
import net.jxta.myjxta.ui.action.GroupCreateAction;
import net.jxta.myjxta.ui.graph.JxtaNodeLoader;
import net.jxta.myjxta.ui.model.JxtaTreeModelListener;
import net.jxta.myjxta.util.Resources;
import net.jxta.myjxta.util.objectmodel.JxtaNode;
import net.jxta.myjxta.util.objectmodel.MyJxtaObjectRepository;
import net.jxta.myjxta.util.preferences.LegacyPreferenceSet;
import javax.swing.*;

import java.awt.*;
import java.awt.event.*;
import java.util.ResourceBundle;

/**
 *
 * @version $Id: Graph.java,v 1.8 2006/10/09 22:17:52 nano Exp $
 *
 * @author james todd [gonzo at jxta dot org]
 */

public final class Graph
        extends JDialog {
    
    private static final int WIDTH = 400;
    private static final int HEIGHT = WIDTH;
    private static final int PAN = 175;
    private static final ResourceBundle STRINGS = Resources.getStrings();
    
    private MyJXTA myjxta = null;
    private MyJXTAView view = null;
    private ItemRegistry registry = null;
    private JxtaNodeLoader loader = null;
    private JxtaTreeModelListener listener = null;
    private ActionList filter = null;
    private ActionList forces = null;
    private ActionList layout = null;
    private ActionList update = null;
    private ActionList animate = null;
    private Tree t = null;
    private final ForcesAndUpdateColorFunction fu_colorFunction;
    
    
    public Graph(MyJXTA myjxta) {
        // xxx: this will not work for non Swing apps
        super((MyJXTAView)myjxta.getView());
        
        this.myjxta = myjxta;
        // xxx: won't work for non-swing apps
        this.view = (MyJXTAView)this.myjxta.getView();
        
        //Tree t = new DefaultTree();
        t = new DefaultTree();
        
        this.registry = new ItemRegistry(t);
        this.loader = new JxtaNodeLoader(this.registry);
        
        Node root = this.loader.loadNode(GraphLoader.LOAD_CHILDREN, null,
                MyJxtaObjectRepository.getRoot());
        
        t.setRoot((TreeNode)root);
        
        this.loader.addGraphLoaderListener(new GraphLoaderListener() {
            public void entityLoaded(GraphLoader loader, Entity e) {
                process();
            }
            
            public void entityUnloaded(GraphLoader loader, Entity e) {
                process();
            }
            
            private void process() {
                //forces.cancel();
                filter.runNow();
                layout.runNow();
                
                //forces.runNow();
            }
        });
        
        fu_colorFunction = new ForcesAndUpdateColorFunction();
        
        
        
        applyRenderers();
        
        applyLayout();
        
        applyAnimation();
        
        applyUpdateAndFilters();
        
        applyForces();
        
        Display display = new Display();
        
        display.setItemRegistry(this.registry);
        display.setPreferredSize(new Dimension(WIDTH, HEIGHT));
        display.pan(PAN, PAN);
        display.addControlListener(new FocusControl(layout));
        display.addControlListener(new NeighborHighlightControl(update));
        display.addControlListener(new DragControl());
        display.addControlListener(new ControlAdapter() {
//            private boolean wasFixed = false;
            
            public void itemEntered(VisualItem vi, MouseEvent me) {
                ((Display)me.getSource()).
                        setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
                
                //wasFixed = vi.isFixed();
                
                //vi.setFixed(true);
            }
            
            public void itemExited(VisualItem vi, MouseEvent me) {
                ((Display)me.getSource()).setCursor(Cursor.getDefaultCursor());
                
                //vi.setFixed(wasFixed);
            }
            
            public void itemReleased(VisualItem vi, MouseEvent me) {
                //vi.setFixed(wasFixed);
            }
        });
        display.addControlListener(new PanControl());
        display.addControlListener(new ZoomControl());
        
        this.registry.getDefaultFocusSet().addFocusListener(new FocusListener() {
            public void focusChanged(FocusEvent e) {
                NodeItem n = registry.getNodeItem((Node)e.getFirstRemoved());
                
                if (n != null) {
                    //n.setFixed(false);
                }
                
                filter.runNow();
            }
        });
        this.registry.getDefaultFocusSet().set(root);
        
        MyJxtaObjectRepository.getObjectRepository().addListener(this.listener =
                new JxtaTreeModelListener() {
            public void nodeAdded(JxtaNode n) {
                loader.loadNode(GraphLoader.LOAD_PARENT, n);
            }
            
            public void nodeRemoved(JxtaNode n) {
                // xxx: implement
            }
            
            public void nodeUpdated(JxtaNode n) {
                // xxx: implement
            }
        });
        
        /** detach from backing tree */
        addWindowListener(new WindowAdapter() {
            public void windowClosing(WindowEvent e) {
                e.getWindow().setVisible(false);
                shutdownGraph();
                super.windowClosing(e);
            }

            public void windowClosed(WindowEvent we) {
                shutdownGraph();
            }
            
        });
        
        /** run layout on window resize */
        addComponentListener(new ComponentAdapter() {
            public void componentResized(ComponentEvent e) {
                layout.runNow();
            }

        });
        
        setTitle(STRINGS.getString("title.graph"));
        setDefaultCloseOperation(DISPOSE_ON_CLOSE);
        setLocation(0,0);
        //setLocationRelativeTo(this.view);
        // xxx: jre 1.5
        setAlwaysOnTop(false);
        	
        	
        	
        getContentPane().add(display);
        
        pack();
        
        setVisible(true);
       
        while (display.getGraphics() == null) {
        }
        
        this.filter.runNow();
        this.layout.runNow();
        //this.forces.runNow();
    }

    private void shutdownGraph() {
        if (listener!=null){
            MyJxtaObjectRepository.getObjectRepository().removeListener(listener);
            listener=null;
        }
    }

    private void applyLayout() {
        
        layout = new ActionList(registry);
        layout.add(new TreeFilter(false, true, true));
        layout.add(new RadialTreeLayout());
        layout.add(new LayoutColorFunction());
        
    }
    
    private void applyAnimation() {
        
        animate = new ActionList(registry,1500,20);
        animate.setPacingFunction(new SlowInSlowOutPacer());
        animate.add(new PolarLocationAnimator());
        animate.add(new ColorAnimator());
        animate.add(new RepaintAction());
        animate.alwaysRunAfter(layout);
        
    }
    private void applyUpdateAndFilters() {
        
        update = new ActionList(registry);
        update.add(fu_colorFunction);
        update.add(new RepaintAction());
        
        filter = new ActionList(registry);
        filter.add(new FisheyeTreeFilter(-5));
        filter.add(new TreeEdgeFilter());
        
    }
    
    private void applyForces() {
        
        ForceSimulator fs = new ForceSimulator();
        
        fs = new ForceSimulator();
        fs.addForce(new NBodyForce(-0.6f, 15f, 3f));
        fs.addForce(new DragForce());
        
        forces = new ActionList(registry, 1000);
        forces.add(new ForceDirectedLayout(fs, true));
        forces.add(fu_colorFunction);
        
        forces.add(new RepaintAction());
        forces.alwaysRunAfter(animate);
        
    }
    
    
    /** Setup Node and Edge Renderers for items in the registry. Images are
     *  taken from resouces. The node items use the same images as jxtatreetable
     */
    private void applyRenderers() {
        
        TextImageItemRenderer nr = new TextImageItemRenderer() {
            final BasicStroke m_stroke = new BasicStroke(5);
            public BasicStroke getStroke(VisualItem item) {
                return m_stroke;
            } //
        };
        
        nr.setHorizontalAlignment(TextImageItemRenderer.ALIGNMENT_LEFT);
        nr.setVerticalAlignment(TextImageItemRenderer.ALIGNMENT_CENTER);
        
        nr.setHorizontalPadding(3);
        nr.setVerticalPadding(0);
        nr.setMaxImageDimensions(40,40);
        nr.setRoundedCorner(8, 8);
        nr.setRenderType(ShapeRenderer.RENDER_TYPE_FILL);
        /** associates attribute id and item label */
        nr.setTextAttributeName(JxtaNodeLoader.LABEL);
        /** associates attribute id and item image */
        nr.setImageAttributeName(JxtaNodeLoader.IMAGE);
        
        
        /** add images into prufuse resources */
        Resources res = Resources.getInstance();
        
        nr.getImageFactory().addImage("TreeTable.PeerGroup",
                res.getIconResource("TreeTable.PeerGroupJoined").getImage());
        nr.getImageFactory().addImage("TreeTable.PeerPresent",
                res.getIconResource("TreeTable.PeerPresent").getImage());
        nr.getImageFactory().addImage("TreeTable.Share",
                res.getIconResource("TreeTable.Share").getImage());
        
        DefaultEdgeRenderer er = new DefaultEdgeRenderer();
        /**push the renderers into registry */
        registry.setRendererFactory(new DefaultRendererFactory(
                nr, er, null));
    }
    
    
    
    public final class RadialColorFunction extends ColorFunction {
        private final Color graphEdgeColor = Color.LIGHT_GRAY;
        private final Color highlightColor = new Color(50,50,255);
        private final Color focusColor = new Color(255,50,50);
        private final ColorMap colorMap;
        
        public RadialColorFunction(int thresh) {
            colorMap = new ColorMap(
                    ColorMap.getInterpolatedMap(thresh+1, Color.RED, Color.BLACK),
                    0, thresh);
        } //
        
        public Paint getFillColor(VisualItem item) {
            if ( item instanceof NodeItem ) {
                return Color.WHITE;
            } else if ( item instanceof AggregateItem ) {
                return Color.LIGHT_GRAY;
            } else if ( item instanceof EdgeItem ) {
                return getColor(item);
            } else {
                return Color.BLACK;
            }
        } //
        
        public Paint getColor(VisualItem item) {
            if ( item.isFocus() ) {
                return focusColor;
            } else if ( item.isHighlighted() ) {
                return highlightColor;
            } else if (item instanceof NodeItem) {
                int d = ((NodeItem)item).getDepth();
                return colorMap.getColor(d);
            } else if (item instanceof EdgeItem) {
                EdgeItem e = (EdgeItem) item;
                if ( e.isTreeEdge() ) {
                    int d, d1, d2;
                    d1 = ((NodeItem)e.getFirstNode()).getDepth();
                    d2 = ((NodeItem)e.getSecondNode()).getDepth();
                    d = Math.max(d1, d2);
                    return colorMap.getColor(d);
                } else {
                    return graphEdgeColor;
                }
            } else {
                return Color.BLACK;
            }
        } //
    } // end of inner class RadialColorFunction
    
    
    public final class ForcesAndUpdateColorFunction extends ColorFunction {
        private final Color pastelRed = new Color(221,238,240);
        private final Color pastelOrange = new Color(242, 215, 121);
//        private Color lightGray = new Color(220, 220, 255);
        
        public Paint getColor(VisualItem vi) {
            Paint p = null;
            
            if (vi instanceof EdgeItem) {
                if (vi.isHighlighted()) {
                    p = pastelOrange;
                } else {
                    p = Color.LIGHT_GRAY;
                }
            } else {
                p = Color.BLACK;
            }
            
            return p;
        }
        
        public Paint getFillColor(VisualItem vi) {
            Paint p = null;
            
            if (vi.isHighlighted()) {
                p = pastelOrange;
            } else if (vi instanceof NodeItem) {
                
                p = pastelRed;
                
            } else {
                p = Color.BLACK;
            }
            
            return p;
        }
    }
    
    public final class LayoutColorFunction extends ColorFunction {
//        private Color pastelRed = Color.RED;; //new Color(255, 125, 125);
        private final Color pastelOrange = Color.ORANGE;// new Color(255, 200, 125);
//        private Color lightGray = Color.BLUE; //new Color(220, 220, 255);
        
        public Paint getColor(VisualItem vi) {
            Paint p = null;
            
            if (vi instanceof EdgeItem) {
                if (vi.isHighlighted()) {
                    p = pastelOrange;
                } else {
                    p = Color.LIGHT_GRAY;
                }
            } else {
                p = Color.BLACK;
            }
            
            return p;
        }
        
        public Paint getFillColor(VisualItem vi) {
            Paint p = null;
            
            if (vi.isHighlighted()) {
                p = pastelOrange;
            } else if (vi instanceof NodeItem) {
                
                p = Color.WHITE;
                
                
                
            } else {
                p = Color.BLACK;
            }
            
            return p;
        }
    }
    
    
    
}
